#include "stdafx.h"
#include <algorithm>
#include "h2base.h"
#include "h2log.h"
#include "text_container.h"

#pragma comment(lib, "Gdi32.lib")
#pragma comment(lib, "User32.lib")

text_container::text_container() : _caret_pen(NULL)
{
    memset(&_view_pos, 0, sizeof(_view_pos));
    memset(&_scroll_pos, 0, sizeof(_scroll_pos));
    memset(&_margin, 0, sizeof(_margin));
    set_selection_color(RGB(224,224,196), RGB(96,96,96));
    set_mark_color(RGB(224,224,196), RGB(24,64,24));
    set_caret_color(RGB(0,255,0), RGB(128,128,128));
    _text_buffer.set_grow_size(10240);
    _canvas_dirty = false;
    clear_text();
}


text_container::~text_container()
{
    _canvas.release();
    clear_text();

    if(_caret_pen)
    {
        DeleteObject(_caret_pen);
        LOG::debug(DEBUG_EDIT | DEBUG_L3, __FUNCTION__ "() release caret pen=%X.", _caret_pen);
        _caret_pen = NULL;
    }

    for(int i = 0; i < _text_styles.length(); ++i)
    {
        if(_text_styles[i].font)
        {
            DeleteObject(_text_styles[i].font);
        }
    }
    _text_styles.clear();
}

void text_container::debug_info()
{
    std::string output;

    char tmp[MAX_STR_BUF];
    wchar_t wtmp[MAX_STR_BUF];

    LOG::debug(DEBUG_EDIT | DEBUG_L4, __FUNCTION__ 
        "() _max_canvas_width=%d, view_x=%d, view_y=%d", _max_canvas_width, _view_pos.x, _view_pos.y);

    int count = 0;
    for(count = 0; count < _line_buffer.length(); ++count)
    {
        LOG::debug(DEBUG_EDIT | DEBUG_L4, 
            "  line %d: begin=%d  end=%d  x=%d  y=%d  width=%d  height=%d", 
            count, _line_buffer[count].range.begin,  _line_buffer[count].range.end, 
            _line_buffer[count].x, _line_buffer[count].y, _line_buffer[count].width, _line_buffer[count].height);

        for(int i = _line_buffer[count].range.begin; i < _line_buffer[count].range.end && i < _text_buffer.length(); ++i)
        {
            if(_text_buffer[i].unicode == '\n')
            {
                wsprintf(wtmp, L"<cr>.%d.%d  ", _text_buffer[i].width, _text_buffer[i].height);
            }
            else
            {
                wsprintf(wtmp, L"[%c].%d.%d  ", _text_buffer[i].unicode, _text_buffer[i].width, _text_buffer[i].height);
            }

            WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, wtmp, -1, tmp, sizeof(tmp), NULL, NULL);
            output += tmp;
        }

        output += "\n";
    }

    LOG::debug(DEBUG_EDIT | DEBUG_L4, "_text_buffer.length() = %d \n%s", _text_buffer.length(), output.c_str());


    output = "";
    for(int itm = 0; itm < mark_count(); ++itm)
    {
        sprintf_s(tmp, "[%d]<%d-%d>  ", itm + 1, mark_at(itm).begin, mark_at(itm).end);
        output += tmp;
    }

    LOG::debug(DEBUG_EDIT | DEBUG_L4, "mark range = %d\n%s", mark_count(), output.c_str());

    output = "";
    for(int its = 0; its < selection_count(); ++its)
    {
        sprintf_s(tmp, "[%d]<%d-%d>  ", its + 1, mark_at(its).begin, mark_at(its).end);
        output += tmp;
    }

    LOG::debug(DEBUG_EDIT | DEBUG_L4, "select range = %d\n%s", mark_count(), output.c_str());

    //LOG::debug(DEBUG_EDIT | DEBUG_L4, "lines debug info\n%s", _line_buffer.debug_info().c_str());

    LOG::debug(DEBUG_EDIT | DEBUG_L4, "caret=%d\n", caret_pos());
}


void text_container::insert_text(int pos, const char* pszText, int style)
{
    if(NULL == pszText)
    {
        LOG::debug(DEBUG_EDIT | DEBUG_L3, __FUNCTION__ "() invalid parameter pszText = NULL.");
        return;
    }

    int len = MultiByteToWideChar(CP_ACP, MB_COMPOSITE, pszText, -1, NULL, 0);

    if(len == 0)
    {
        return;
    }

    // convert to wchar_t string
    STRING::unicode txt(pszText);
    insert_text(pos, *txt);
}


void text_container::insert_text(int pos, const wchar_t* pwszText, int style)
{
    if(NULL == pwszText)
    {
        LOG::debug(DEBUG_EDIT | DEBUG_L3, __FUNCTION__ "() invalid parameter pwszText = NULL.");
        return;
    }

    int len = wcslen(pwszText);
    
    if(len == 0)
    {
        return;
    }

    STRING::string<text_char> text(len);

    for(int i = 0; i < len; ++i)
    {
        text[i].unicode = pwszText[i];
        text[i].style = style;
        if(!_canvas.measure_font(_text_styles[text[i].style].font, text[i].unicode, &text[i].width, &text[i].height))
        {
            text[i].width = 8;
            text[i].height = 16;
        }
    }

    insert_text(pos, len, *text);
}


void text_container::insert_text(int pos, int length, text_char* ptcText, int style)
{
    // parameters verification
    if(NULL == ptcText || length < 0)
    {
        LOG::debug(DEBUG_EDIT | DEBUG_L3, __FUNCTION__ "() invalid parameter pos=%d, length=%d, ptcText=%X, style=%d.", pos, ptcText, length, style);
        return;
    }
    if(pos < 0)
    {
        LOG::debug(DEBUG_EDIT | DEBUG_L3, __FUNCTION__ "() invalid parameter pos=%d, length=%d.", pos, length);
        // no need return
    }


    // create the first line in case of empty content
    if(_text_buffer.length() == 0 || _line_buffer.length() == 0)
    {
        clear_text();
    }


    // initialize iterators
    int insert_pos = max(0, min(pos, _text_buffer.length()));
    int insert_line = line_from_pos(insert_pos);

    if(insert_line < 0 || insert_line >= _line_buffer.length())
    {
        LOG::debug(DEBUG_EDIT | DEBUG_L3, __FUNCTION__ "() cannot find line of pos=%d, result=%d.", pos, insert_line);
        return;
    }

    // insert text characters one by one
    text_line* pline = &_line_buffer[insert_line];
    for(int inserted = 0; inserted < length; ++inserted)
    {
        // insert character
        //  TTTTTTTTTTTTTTTTTT
        //# AAAAAAAA*IBBBBBBBBB
        //  TTTTTTTTTTTTTTTTTT
        _text_buffer.insert(insert_pos + inserted, ptcText + inserted, 1);
        pline->range.end ++;
        

        // handle new line
        //  TTTTTTTTTTTTTTTTTT
        //# AAAAAAAA*\n
        //  BBBBBBBBB  
        //  TTTTTTTTTTTTTTTTTT
        if(ptcText[inserted].unicode == '\n')
        {
            // construct new line
            text_line line;
            line.x = 0;
            line.y = pline->y + pline->height;
            line.width = 0;
            line.height = 0;
            line.range.begin = insert_pos + inserted + 1;
            line.range.end = pline->range.end;

            // cut off current line
            pline->range.end = insert_pos + inserted + 1;
            measure_line(*pline, &pline->width, &pline->height);
            pline->dirty = true;

            // insert new line
            _line_buffer.insert(insert_line + 1, &line, 1);
            insert_line++;
            pline = &_line_buffer[insert_line];
            pline->dirty = true;
            measure_line(*pline, &pline->width, &pline->height);
        }
    }

    // trigger modified event
    on_modified(insert_pos, length);
}


void text_container::remove_text(const text_range& range)
{
    if(_text_buffer.length() == 0 || _line_buffer.length() == 0 || range.begin < 0 || range.begin >= _text_buffer.length())
    {
        return;
    }
    
    int iBeginPos = max(0, min(range.begin, _text_buffer.length() - 1));
    int iEndPos = max(0, min(range.end - 1, _text_buffer.length() - 1));
    int iBeginLine = line_from_pos(iBeginPos);
    int iEndLine = line_from_pos(iEndPos);
    int iErased = iEndPos - iBeginPos + 1;

    //  TTTTTTTTTT      
    //# AAAA*DDDDD      # - iBeginLine, * - iBeginPos 
    //% DDDDD$BBBB      % - iEndLine,   $ - iEndPos
    //  XXXXXXXXXX

    //  TTTTTTTTTT     TTTTTTTTTT
    //# AAAA*DDDDD     AAAABBBB     # - iBeginLine, * - iBeginPos
    //% DDDDD$BBBB  =>              % - iEndLine,   $ - iEndPos
    //  XXXXXXXXXX     XXXXXXXXXX
    _text_buffer.remove(iBeginPos, iErased);
    // removed text within one line
    if(iBeginLine == iEndLine)
    {
        _line_buffer[iBeginLine].range.end -= iErased;
    }
    // removed text cross lines
    else
    {
        _line_buffer[iBeginLine].range.end = _line_buffer[iEndLine].range.end - iErased;
        _line_buffer.remove(iBeginLine + 1, iEndLine - iBeginLine);
    }

    // trigger modified event
    on_modified(iBeginPos, -iErased);

    redraw_canvas(true);
}

void text_container::remove_sel_text()
{
    for(int i = 0; i < _select_scope.ranges.length(); ++i)
    {
        remove_text(_select_scope.ranges[i]);
    }
    clear_selection();
}



STRING::unicode text_container::get_text(const text_range& range)
{
    int pos = max(min(range.begin, _text_buffer.length()), 0);
    int length = range.end - pos;

    if(length <= 0)
    {
        return L"";
    }
    STRING::unicode text(length);

    for(int count = 0; count <length && pos < _text_buffer.length(); ++pos)
    {
        text[count++] = _text_buffer[pos].unicode;
    }

    return text;
}

STRING::unicode text_container::get_line(int line)
{
    if(line < 0 || line > _line_buffer.length())
    {
        return L"";
    }

    return get_text(_line_buffer[line].range);
}


void text_container::clear_text()
{
    if(_text_buffer.length() > 0)
    {
        _canvas_dirty = true;
    }
    _text_buffer.clear();
    _line_buffer.clear();
    text_line tmpline;
    memset(&tmpline, 0, sizeof(text_line));
    _line_buffer.insert(0, &tmpline, 1);
    _line_buffer.back().dirty = true;
}


void text_container::select_text(const text_range& range, bool clear_first)
{
    if(clear_first)
    {
        clear_selection();
    }

    if(range.begin >= range.end)
    {
        return;
    }

    // merge with existing ranges
    text_range new_range = { range.begin, range.end };

    for(int i = 0; i < _select_scope.ranges.length(); ++i)
    {
        while(i < _select_scope.ranges.length() && mergeable_range(selection_at(i), new_range))
        {
            new_range.begin = min(new_range.begin, _select_scope.ranges[i].begin);
            new_range.end = max(new_range.end, _select_scope.ranges[i].end);
            _select_scope.ranges.remove(i);
        }
    }

    // insert new range
    int j = 0;
    for(j = 0; j < _select_scope.ranges.length(); ++j)
    {
        if(_select_scope.ranges[j].begin > new_range.begin)
        {
            _select_scope.ranges.insert(j, &new_range, 1);
            break;
        }
    }

    if(j >= _select_scope.ranges.length())
    {
        _select_scope.ranges.push_back(new_range);
    }


    dirty_scope(_select_scope);

    redraw_canvas(true);
}


int text_container::mark_text(const RECT& rcmark, bool single_range, bool full_line)
{
    int iline = 0, imark = 0;

 
    // clear old marks
    dirty_scope(_mark_scope);
    clear_scope(_mark_scope);

    text_range markrange = {0, 0};

    // construct new mark range
    for(iline = 0; iline < _line_buffer.length(); ++iline)
    {
        // skip the lines if y position not overlapped
        if(_line_buffer[iline].y + _line_buffer[iline].height < rcmark.top || _line_buffer[iline].y > rcmark.bottom)
        {
            continue;
        }

        // initialize the markrange
        if(markrange.end == 0)
        {
            if(full_line)
            {
                markrange.begin = _line_buffer[iline].range.begin;
            }
            else
            {
                markrange.begin = _line_buffer[iline].range.end;
            }
        }
        if(full_line)
        {
            markrange.end = _line_buffer[iline].range.end;
        }

        // calculate the text in mark range
        int x = 0, y = _line_buffer[iline].y, height = 0;
        for(int ichar = _line_buffer[iline].range.begin; ichar < _line_buffer[iline].range.end; ++ichar)
        {
            if(x + _text_buffer[ichar].width > _max_canvas_width)
            {
                x = 0;
                y += height;
            }
            height = max(height, _text_buffer[ichar].height);

            RECT rcchar = { x, y, x + _text_buffer[ichar].width, y + _text_buffer[ichar].height };

            // calculate the mark ranges by scope
            if(!(rcmark.right < rcchar.left || rcmark.left > rcchar.right ||
                rcmark.bottom < rcchar.top || rcmark.top > rcchar.bottom))
            {
                if(_mark_scope.ranges.length() == 0 || _mark_scope.ranges.back().end != ichar)
                {
                    text_range tmprange = {ichar, ichar + 1};
                    _mark_scope.ranges.push_back(tmprange);
                }
                else
                {
                    _mark_scope.ranges.back().end ++;
                }
            }

            // calculate the mark ranges by whole lines
            // char on the left
            if(rcchar.left <= rcmark.right && !(rcmark.bottom < rcchar.top || rcmark.top > rcchar.bottom))
            {
                markrange.end = max(markrange.end, ichar + 1);
            }
            // char on the right
            if(rcchar.right >= rcmark.left && !(rcmark.bottom < rcchar.top || rcmark.top > rcchar.bottom))
            {
                markrange.begin = min(markrange.begin, ichar);
            }


            x += _text_buffer[ichar].width;
        }

    }

    if(single_range)
    {
        _mark_scope.ranges.clear();
        _mark_scope.ranges.push_back(markrange);
    }

    // dirtying new marked lines
    dirty_scope(_mark_scope);

    return _mark_scope.ranges.length();
}


int text_container::pos_from_point(int x, int y)
{
    int cx = view_to_canvas_x(x);
    int cy = view_to_canvas_y(y);
    int pos = -1;

    for(int itline = 0; itline < _line_buffer.length(); ++itline)
    {
        int lx = 0, ly = _line_buffer[itline].y, max_height = 0;

        // skip if the text line is out of view
        if(cy < ly || cy > ly + _line_buffer[itline].height)
        {
            continue;
        }

        // calculate position of each characters
        int itc = _line_buffer[itline].range.begin;

        for(int i = _line_buffer[itline].range.begin; i < _line_buffer[itline].range.end; ++i, ++itc)
        {
            if(lx + _text_buffer[itc].width > _max_canvas_width)
            {
                lx = 0;
                ly += max_height;
            }
            max_height = max(max_height, _text_buffer[itc].height);

            if(cy >= ly && cy < ly + _text_buffer[itc].height)
            {
                if(cx >= lx && cx < lx + _text_buffer[itc].width)
                {
                    return i;
                }
                if(cx < lx)
                {
                    return i;
                }
                pos = i;
            } 
            lx += _text_buffer[itc].width;
        }
    }

    if(pos >= 0 && pos == _text_buffer.length() - 1)
    {
        return pos + 1;
    }
    return pos;
}

bool text_container::word_range_from_pos(int pos, text_range* pRange)
{
    if(pos < 0 || pos > _text_buffer.length() || NULL == pRange)
    {
        return false;
    }

    for(pRange->begin = pos; pRange->begin > 0; --pRange->begin)
    {
        if(_text_buffer[pRange->begin - 1].unicode <= 0x20)
        {
            break;
        }
    }

    for(pRange->end = pos; pRange->end < _text_buffer.length(); ++pRange->end)
    {
        if(_text_buffer[pRange->end].unicode <= 0x20)
        {
            break;
        }
    }
    return pRange->end > pRange->begin;
}


int text_container::line_from_pos(int pos)
{
    int iPos = min(max(pos, 0), _text_buffer.length());

    if(iPos == _text_buffer.length())
    {
        return _line_buffer.length() - 1;
    }

    for(int count = 0; count < _line_buffer.length(); ++count)
    {
        // not itl->range.begin <= pos && itl->range.end > pos
        if(_line_buffer[count].range.begin >= pos || _line_buffer[count].range.end > pos)
        {
            return count;
        }
    }

    return -1;
}


void text_container::move_caret(int pos)
{
    int iValidPos = max(0, min(pos, _text_buffer.length()));

    if(_caret_pos == iValidPos)
    {
        return;
    }

    int iOldLine = line_from_pos(_caret_pos);
    int iNewLine = line_from_pos(iValidPos);

    _caret_pos = iValidPos;

    if(iOldLine >= 0)
    {
        _line_buffer[iOldLine].dirty = true;
    }

    if(iNewLine >= 0 && iNewLine != iOldLine)
    {
        _line_buffer[iNewLine].dirty = true;
    }

    redraw_canvas(true);
}


void text_container::set_caret_mode(bool overtype)
{
    if(_caret_overtype == overtype)
    {
        return;
    }

    _caret_overtype = overtype;

    int iCaretLine = line_from_pos(_caret_pos);
    if(iCaretLine >= 0 && iCaretLine < _line_buffer.length())
    {
        _line_buffer[iCaretLine].dirty = true;
    }

    redraw_canvas(true);
}


void text_container::show_caret(bool showed)
{
    if(_caret_show == showed)
    {
        return;
    }

    _caret_show = showed;

    int iCaretLine = line_from_pos(_caret_pos);
    if(iCaretLine >= 0 && iCaretLine < _line_buffer.length())
    {
        _line_buffer[iCaretLine].dirty = true;
    }

    redraw_canvas(true);
}














































bool text_container::measure_line(const text_line& tline,  int* piWidth, int* piHeight)
{
    if(tline.range.begin < 0 || tline.range.begin >= _text_buffer.length() || !piWidth || !piHeight)
    {
        return false;
    }

    int itc = tline.range.begin;
    *piWidth = 0;
    *piHeight = 0;
    int iTmpWidth = 0;
    int iTmpHeight = 0;

    for(int i = tline.range.begin; i < tline.range.end && itc != _text_buffer.length(); ++i, ++itc)
    {
        if(iTmpWidth + _text_buffer[itc].width > _max_canvas_width)
        {
            *piWidth = max(*piWidth, iTmpWidth);
            *piHeight += iTmpHeight;
            iTmpWidth = 0;
            iTmpHeight = 0;
        }

        iTmpWidth += _text_buffer[itc].width;
        iTmpHeight = max(_text_buffer[itc].height, iTmpHeight);
    }

    *piWidth = max(*piWidth, iTmpWidth);
    *piHeight += iTmpHeight;

    return true;
}

void text_container::dirty_scope(text_scope& scope)
{
    int il = 0, im = 0;
    for(; im < scope.ranges.length(); ++im)
    {
        for(; il < _line_buffer.length(); ++il)
        {
            // mark in the line
            if(overlapped_range(scope.ranges[im], _line_buffer[il].range))
            {
                _line_buffer[il].dirty = true;
                _canvas_dirty = true;
                continue;
            }

            // line > mark
            if(scope.ranges[im].end <= _line_buffer[il].range.begin)
            {
                break;
            }
        }
    }
}


STRING::unicode text_container::get_scope_text(text_scope& scope)
{
    STRING::unicode text;

    for(int i = 0; i < scope.ranges.length(); ++i)
    {
        text = text + get_text(scope.ranges[i]);
        if(i < scope.ranges.length() - 1 && char_at(scope.ranges[i].end - 1) != '\n')
        {
            text = text + STRING::unicode("\n");
        }
    }

    return text;
}




int text_container::add_style(COLORREF cltext, const wchar_t* fontfamily, int size)
{
    text_style style;
    memset(&style, 0, sizeof(style));
    style.cltext = cltext;
    style.width = (size+1) / 2;
    style.height = size;
    style.weight = FW_MEDIUM;
    style.charset = GB2312_CHARSET;

    if(fontfamily)
    {
        wcscpy_s(style.fontfamily, fontfamily);
    }

    _text_styles.push_back(style);
    update_style(_text_styles.length() - 1);

    return _text_styles.length() - 1;
}

void text_container::update_style(int style)
{

    LOGFONT lf = {_text_styles[style].height, _text_styles[style].width, 0, 0, 
        _text_styles[style].weight, _text_styles[style].italic, _text_styles[style].underline, _text_styles[style].strikeout, 
        _text_styles[style].charset, 0, 0, PROOF_QUALITY, FIXED_PITCH, L"Arial"};

    wcscpy_s(lf.lfFaceName, _text_styles[style].fontfamily);

    if(_text_styles[style].font)
    {
        DeleteObject(_text_styles[style].font);
    }
    _text_styles[style].font = CreateFontIndirect(&lf);
}


bool text_container::init_canvas(HWND hWnd)
{
    _canvas.release();
    _canvas.init(hWnd);
    _canvas.resize(GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));

    // add default style
    if(_text_styles.length() == 0)
    {
        add_style(RGB(0,224,0), L"Arial", 14); 
    }

    // set default text style
    set_background_color(RGB(0,0,0));

    return true;
}


void text_container::set_max_canvas_width(int width)
{
    if(width - _margin.left - _margin.right == _max_canvas_width)
    {
        return;
    }

    _max_canvas_width = width - _margin.left - _margin.right; 

    int y = 0, tmp_width, tmp_height;
    for(int itl = 0; itl < _line_buffer.length(); ++itl)
    {
        measure_line(_line_buffer[itl], &tmp_width, &tmp_height);
        if(_line_buffer[itl].y != y || _line_buffer[itl].width != tmp_width || _line_buffer[itl].height != tmp_height)
        {
            _line_buffer[itl].y = y;
            _line_buffer[itl].width = tmp_width;
            _line_buffer[itl].height = tmp_height;
            _line_buffer[itl].dirty = true;
        }
        y += _line_buffer[itl].height;
    }
}


void text_container::redraw_canvas(bool dirty_only)
{
    RECT rcCaret = { 0, 0, 0, 0 };

    RECT rcTopMargin = { 0, 0, _canvas.width(), _margin.top };
    _canvas.erase(&rcTopMargin);

    for(int itl = 0; itl < _line_buffer.length(); ++itl)
    {
        if(dirty_only == false || _line_buffer[itl].dirty)
        {
            _line_buffer[itl].dirty = false;

            // skip if the text line is out of view
            if(canvas_to_view_y(_line_buffer[itl].y + _line_buffer[itl].height) <= _margin.top || canvas_to_view_y(_line_buffer[itl].y) >= _canvas.height() - _margin.bottom)
            {
                continue;
            }

            // erase the line background
            RECT rcLine = {0, canvas_to_view_y(_line_buffer[itl].y), _canvas.width(), canvas_to_view_y(_line_buffer[itl].y + _line_buffer[itl].height)};
            _canvas.erase(&rcLine);

            // draw characters
            int x = 0, y = _line_buffer[itl].y, height = 0;
            for(int i = _line_buffer[itl].range.begin; i < _line_buffer[itl].range.end; ++i)
            {
                text_line line;
                memcpy(&line, &_line_buffer[itl], sizeof(text_char));
                if(x + _text_buffer[i].width > _max_canvas_width)
                {
                    x = 0;
                    y += height;
                }
                height = max(height, _text_buffer[i].height);

                if(canvas_to_view_x(x + _text_buffer[i].width) > _margin.left && canvas_to_view_x(x) < _canvas.width() - _margin.right &&
                    canvas_to_view_y(y + _text_buffer[i].height) > _margin.top && canvas_to_view_y(y) < _canvas.height() - _margin.bottom)
                {
                    COLORREF cltext, clback;

                    // set text color
                    if(is_selected(i))
                    {
                        cltext = _sel_text_color;
                        clback = _sel_bg_color;
                    }
                    else if(is_marked(i))
                    {
                        cltext = _mark_text_color;
                        clback = _mark_bg_color;
                    }
                    else
                    {
                        cltext = _text_styles[_text_buffer[i].style].cltext;
                        clback = _text_styles[_text_buffer[i].style].clback;
                    }

                    _canvas.draw_text(canvas_to_view_x(x), canvas_to_view_y(y), cltext, clback, 
                        _text_styles[_text_buffer[i].style].transparent, _text_styles[_text_buffer[i].style].font, _text_buffer[i]);

                    // save the position of caret
                    if(_caret_pos == i)
                    {
                        rcCaret.left = canvas_to_view_x(x);
                        rcCaret.top = canvas_to_view_y(y);
                        rcCaret.right = rcCaret.left + _text_buffer[i].width;
                        rcCaret.bottom = rcCaret.top + _text_buffer[i].height;
                    }
                    if(_caret_pos == _text_buffer.length() && _caret_pos == i + 1)
                    {
                        if(_text_buffer[i].unicode == '\n')
                        {
                            rcCaret.left = canvas_to_view_x(0);
                            rcCaret.top = canvas_to_view_y(y + _text_buffer[i].height);
                        }
                        else
                        {
                            rcCaret.left = canvas_to_view_x(x + _text_buffer[i].width);
                            rcCaret.top = canvas_to_view_y(y);
                        }
                        rcCaret.right = rcCaret.left + _text_buffer[i].height/2;
                        rcCaret.bottom = rcCaret.top + _text_buffer[i].height;
                    }
                }
                x += _text_buffer[i].width;
            }
        }
    }
    
    _canvas.flash();

    // erase the empty area of the view
    RECT rcView = { 0, 0, _canvas.width(), _canvas.height() };
    if(_line_buffer.length() > 0)
    {
        rcView.top = canvas_to_view_y(_line_buffer.back().y + _line_buffer.back().height);
    }
    _canvas.erase(&rcView);


    // draw caret
    if(_caret_show && rcCaret.bottom != rcCaret.top)
    {
        HPEN old_pen = (HPEN)SelectObject(_canvas.handle(), _caret_pen);
        Rectangle(_canvas.handle(), rcCaret.left, rcCaret.top + 1, rcCaret.left+1, rcCaret.bottom);
        SelectObject(_canvas.handle(), old_pen);
    }

    _canvas_dirty = false;
}


void text_container::output_canvas(HDC hdc, int x, int y, int width, int height)
{
    if(_canvas_dirty)
    {
        redraw_canvas(true);
    }
    BitBlt(hdc, x, y, min(_canvas.width(), width), min(_canvas.height(), height), _canvas.handle(), 0, 0, SRCCOPY);
}


int text_container::get_max_v_scroll()
{
    if(_line_buffer.length() == 0)
    {
        return 0;
    }
    
    int max_height = _line_buffer.back().y + _line_buffer.back().height;
    int count_height = 0;
    for(int count = _line_buffer.length() - 1; count >= 0; --count)
    {
        count_height += _line_buffer[count].height;
        if(count_height > _canvas.height() - _margin.top - _margin.bottom)
        {
            return count + 1;
        }
    }

    return 0;
}

int text_container::get_max_h_scroll()
{
    if(_line_buffer.length() == 0)
    {
        return 0;
    }
    
    int max_width = 0;
    for(int itl = 0; itl < _line_buffer.length(); ++itl)
    {
        max_width = max(max_width, _line_buffer[itl].width);
    }

    if(_canvas.width() - _margin.left - _margin.right >= max_width)
    {
        return 0;
    }

    return (max_width - _canvas.width() + _margin.left + _margin.right)/ 8;
}

void text_container::scroll_view(int column, int row) 
{
    if(_line_buffer.length() == 0 || _text_buffer.length() == 0)
    {
        return;
    }

    _scroll_pos.x = max(0, min(column, (_max_canvas_width - _canvas.width())/8));
    _scroll_pos.y = max(0, min(_line_buffer.length()-1, row));

    int x = _scroll_pos.x * 8; 
    int y = _line_buffer[_scroll_pos.y].y;
    if(y != _view_pos.y || x != _view_pos.x)
    {
        _view_pos.x = x;
        _view_pos.y = y;
        redraw_canvas(false);
    }
}



bool text_dc::init(HWND hwnd)
{
    release();

    _hwnd = hwnd;

    HDC hdc = GetDC(hwnd);
    _hdc = CreateCompatibleDC(hdc);
    ReleaseDC(hwnd, hdc);

    if(NULL == _hdc)
    {
        LOG::debug(DEBUG_EDIT | DEBUG_L1, 
            __FUNCTION__ "() invalid handle CreateCompatibleDC(GetDC(0x%X)=0x%X) = NULL.", hwnd, hdc);
        return false;
    }

    LOG::debug(DEBUG_EDIT | DEBUG_L1, __FUNCTION__ "() create DC succeed! _canvas_dc=0x%X.", _hdc);

    return true;
}



void text_dc::release()
{
    if(_old_hbmp)
    {
        SelectObject(_hdc, _old_hbmp);
        _old_hbmp = NULL;
    }

    if(_hbmp)
    {
        DeleteObject(_hbmp);
        LOG::debug(DEBUG_EDIT | DEBUG_L3, __FUNCTION__ "() release bitmap=%X.", _hbmp);
        _hbmp = NULL;
    }

    if(_hdc)
    {
        DeleteDC(_hdc);
        LOG::debug(DEBUG_EDIT | DEBUG_L3, __FUNCTION__ "() release DC=%X.", _hdc);
        _hdc = NULL;
    }

    memset(&_size, 0, sizeof(_size));
    memset(&_bmp_size, 0, sizeof(_bmp_size));
    _text_buffer_len = 0;
}


bool text_dc::resize(int width, int height)
{
    if(NULL == _hdc)
    {
        LOG::debug(DEBUG_EDIT | DEBUG_L3, __FUNCTION__ "() invalid handle _hdc=NULL.");
        return false;
    }

    _size.cx = width;
    _size.cy = height;

    if(_bmp_size.cx >= width && _bmp_size.cy >= height)
    {
        return true;
    }

    _bmp_size.cx = width;
    _bmp_size.cy = height;

    HDC hdc = GetDC(_hwnd);
    HBITMAP hbmp = CreateCompatibleBitmap(hdc, _bmp_size.cx, _bmp_size.cy);
    ReleaseDC(_hwnd, hdc);

    if(!hbmp)
    {
        LOG::debug(DEBUG_EDIT | DEBUG_L3, __FUNCTION__ "() failed to create bitmap (%dx%d).", _bmp_size.cx, _bmp_size.cy);
        return false;
    }

    if(_hbmp)
    {
        SelectObject(_hdc, (HBITMAP)_old_hbmp);
        DeleteObject(_hbmp);
        LOG::debug(DEBUG_EDIT | DEBUG_L3, __FUNCTION__ "() delete previous bitmap=%X.", _hbmp);
    }

    _hbmp = hbmp;
    _old_hbmp = (HBITMAP)SelectObject(_hdc, _hbmp);

    LOG::debug(DEBUG_EDIT | DEBUG_L2, __FUNCTION__ "() create bitmap (%dx%d) succeed! hbmp=0x%X.", _bmp_size.cx, _bmp_size.cy, _hbmp);
    return true;
}


void text_dc::set_bg_color(COLORREF clback)
{
    if(_bgbrush)
    {
        DeleteObject(_bgbrush);
    }

    _bgbrush = CreateSolidBrush(clback);
}

void text_dc::erase(const RECT* prc)
{
    if(NULL != prc)
    {
        FillRect(_hdc, prc, _bgbrush);
    }
    else
    {
        RECT rc = {0, 0, _size.cx, _size.cy};
        FillRect(_hdc, &rc, _bgbrush);
    }
}


void text_dc::flash()
{
    if(_hdc && _text_buffer_len > 0)
    {
        SelectObject(_hdc, _hfont);
        SetTextColor(_hdc, _color_front);
        if(_bg_transparent)
        {
            SetBkMode(_hdc, TRANSPARENT);
        }
        else
        {
            SetBkMode(_hdc, OPAQUE);
            SetBkColor(_hdc, _color_back);
        }
        TextOutW(_hdc, _text_buffer_pos.left, _text_buffer_pos.top, _text_buffer, _text_buffer_len);
    }
    _text_buffer_len = 0;
    memset(&_text_buffer_pos, 0, sizeof(_text_buffer_pos));
}

void text_dc::draw_text(int x, int y, COLORREF cltext, COLORREF clback, bool tranparent, HFONT hfont, const text_char& chr)
{
    // check new char is continiuous or not
    if(_text_buffer_len > 0)
    {
        if(x == _text_buffer_pos.right && y == _text_buffer_pos.top && 
            cltext == _color_front && _bg_transparent == tranparent && clback == _color_back && 
            _hfont == hfont && _text_buffer_len < sizeof(_text_buffer) / sizeof(wchar_t))
        {
            _text_buffer_pos.right += chr.width;
            _text_buffer[_text_buffer_len] = chr.unicode < 0x20 ? 0x20 : chr.unicode;
            _text_buffer_len ++;
            return;
        }

        flash();
    }

    _text_buffer_pos.left = x;
    _text_buffer_pos.top = y;
    _text_buffer_pos.right = x + chr.width;
    _text_buffer_len = 1;
    _text_buffer[_text_buffer_len - 1] = chr.unicode;
    _color_front = cltext;
    _color_back = clback;
    _bg_transparent = tranparent;
    _hfont = hfont;
}


bool text_dc::measure_font(HFONT hfont, wchar_t wchar, unsigned char* pWidth, unsigned char* pHeight)
{
    if(!_hdc)
    {
        return false;
    }

    wchar_t measure_char = wchar < 0x20 ? 0x20 : wchar;

    SIZE size;

    SelectObject(_hdc, hfont);

    if(!GetTextExtentExPointW(_hdc, &measure_char, 1, 0, NULL, NULL, &size))
    {
        *pWidth = 0;
        *pHeight = 0;
        return false;
    }

    *pWidth = (unsigned char)size.cx;
    *pHeight = (unsigned char)size.cy;
    return true;
}


void text_container::on_modified(int pos, int modified_length)
{
    _canvas_dirty = true;

    text_range modified_range = {pos, pos + max(0, modified_length)};

    // update the y position of all lines
    text_line* pline;
    int y = 0, end = 0, offset = 0, i;
    for(i = 0; i < _line_buffer.length(); y += _line_buffer[i].height, end = _line_buffer[i].range.end, ++i)
    {
        pline = &_line_buffer[i];

        // remove the empty line except the last line
        while(pline->range.end == pline->range.begin && i < _line_buffer.length() - 1)
        {
            _line_buffer.remove(i, 1);
            pline = &_line_buffer[i];
        }

        // re-calculate the line was changed
        if(overlapped_range(modified_range, pline->range) || 
            // in case delete at the end of text, the modified_range will be {pline->range.end, pline->range.end}
            // so treat the last line specially
            (i == _line_buffer.length() - 1 && pos == pline->range.end ))
        {
            measure_line(*pline, &pline->width, &pline->height);
            pline->dirty = true;
        }

        // update y position
        if(pline->y != y)
        {
            pline->y = y;
            pline->dirty = true;
        }

        // update range
        if(0 != (offset = pline->range.begin - end))
        {
            pline->range.begin -= offset;
            pline->range.end -= offset;
            pline->dirty = true;
        }
    }

    // update caret position
    if(_caret_pos >= pos)
    {
        _caret_pos = max(pos, _caret_pos + modified_length);
    }

    // update selection
    for(i = 0; i < _select_scope.ranges.length(); ++i)
    {
        if(_select_scope.ranges[i].begin >= pos)
        {
            _select_scope.ranges[i].begin = max(pos, _select_scope.ranges[i].begin + modified_length);
        }
        if(_select_scope.ranges[i].end >= pos)
        {
            _select_scope.ranges[i].end = max(pos, _select_scope.ranges[i].end + modified_length);
        }
    }

}
